/*
This file is part of [ahttp] library. 

Author: Artem Kustikov (kustikoff[at]tut.by)


This code is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this code.

Permission is granted to anyone to use this code for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:

1. The origin of this code must not be misrepresented; you must
not claim that you wrote the original code. If you use this
code in a product, an acknowledgment in the product documentation
would be appreciated but is not required.

2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original code.

3. This notice may not be removed or altered from any source
distribution.
*/

#ifndef AHTTP_SERVER_SETTINGS_H
#define AHTTP_SERVER_SETTINGS_H
#pragma once

#include <stdexcept>
#include <set>
#include <boost/regex.hpp>
#include <boost/function.hpp>

#include "aconnect/types.hpp"
#include "aconnect/complex_types.hpp"
#include "aconnect/logger.hpp"
#include "aconnect/server_settings.hpp"

#include "aconnect/util.string.hpp"

namespace ahttp
{
	using namespace aconnect;

	namespace PluginRegistration
	{
		enum Type {
			Empty,
			Register,
			Add,
			Remove,
			Clear
		};
	};

	enum PluginType 
	{
		PluginHandler,
		PluginModule
	};

	//==================================================================//

	enum ModuleCallbackType
	{
		ModuleCallbackOnRequestBegin = 0,	// directly after HttpContext::init
		ModuleCallbackOnRequestResolve,		// directory info loaded
		ModuleCallbackOnRequestMapHandler,
		ModuleCallbackOnResponsePreSendHeaders,
		ModuleCallbackOnResponsePreSendContent,
		ModuleCallbackOnResponseEnd
	};

	const int ModuleCallbackTypesCount = 6;
	
	//==================================================================//

	namespace SettingsConstants
	{
		string_constant AllExtensionsMark = "*";
		string_constant AppPathMark = "{app-path}";
		
		string_constant ProcessRequestFunName = "processHandlerRequest";
		string_constant InitFunName = "initPlugin";
		string_constant DestroyFunName = "destroyPlugin";

		string_constant OnRequestBeginFunName = "onRequestBegin";		
		string_constant OnRequestResolveFunName = "onRequestResolve";	
		string_constant OnRequestMapHandlerName = "onRequestMapHandler";
		
		string_constant OnResponsePreSendHeadersFunName = "onResponsePreSendHeaders";
		string_constant OnResponsePreSendContentFunName = "onResponsePreSendContent";
		string_constant OnResponseEndFunName = "onResponseEnd";

		string_constant NameMark = "{name}";
		string_constant UrlMark = "{url}";
		string_constant SizeMark = "{size}";
		string_constant TimeMark = "{time}";
		string_constant PageUrlMark = "{page-url}";
		string_constant ParentUrlMark = "{parent-url}";

		string_constant FilesCountMark = "{files-count}";
		string_constant DirectoriesCountMark = "{directories-count}";
		string_constant ErrorsCountMark = "{errors-count}";

		string_constant TabulatorMark = "{tab}";
		string_constant EndOfLineMark = "{eol}";
	}

	//==================================================================//

	struct settings_load_error : public std::runtime_error 
	{
		settings_load_error (string_constptr format, ...) : std::runtime_error ("") 
		{
			if (NULL == format) {
				message_ = "Settings loading failed";
			} else {
				FORMAT_VA_MESSAGE(format, message);
				message_.swap (message);
			}
		}

		virtual ~settings_load_error() throw () { }
				
		virtual const char * what() const throw () {	
			return (message_.c_str());
		}
	private:
		string message_;
	};

	//==================================================================//

	struct PluginRegistrationInfo
	{
		PluginRegistration::Type	registrationType;
		string						pluginName;
		std::set<string>			extensions;
		std::set<string>			excludedExtensions;
		void*						processFunc;	// for handler
		int							pluginIndex;
		void*						moduleCallbacks[ModuleCallbackTypesCount];

		PluginRegistrationInfo() : 
			registrationType (PluginRegistration::Empty),
			processFunc (NULL),
			pluginIndex (-1)
		{
			for (int ndx=0; ndx<ModuleCallbackTypesCount; ++ndx)
				moduleCallbacks[ndx] = NULL;
		}

		inline bool isRequestApplicable (string_constref ext) const
		{
			if (registrationType == PluginRegistration::Empty || registrationType == PluginRegistration::Remove
					|| registrationType == PluginRegistration::Clear)
				return false;

			if (extensions.find (SettingsConstants::AllExtensionsMark) != extensions.end() && excludedExtensions.find (ext) == excludedExtensions.end())
				return true;
			
			return extensions.find (ext) != extensions.end();
		}

	};

	//==================================================================//
	
	struct PluginInfo
	{
		aconnect::library_handle_type	dll;

		string		pathToLoad;
		string		defaultExtension;
		void*		processRequestFunc;
		void*		initFunc;
		void*		destroyFunc;
		int			pluginIndex;
		void*		moduleCallbacks[ModuleCallbackTypesCount];
		bool		global;
		

		aconnect::str2str_map	params;

		PluginInfo() :  
			dll (NULL),
			processRequestFunc (NULL), 
			initFunc (NULL),
			destroyFunc (NULL),
			pluginIndex(0) ,
			global (false)
			{
				for (int ndx=0; ndx<ModuleCallbackTypesCount; ++ndx)
					moduleCallbacks[ndx] = NULL;
			}
	};


	//==================================================================//

	class PluginInfoByNameFinder
	{
	public:
		PluginInfoByNameFinder (string_constref name) : _name (name)	{}
		
		bool operator () (const PluginRegistrationInfo& info) {
			return (aconnect::util::equals (info.pluginName, _name));
		}
		
	protected:
		string _name;
	};

	//==================================================================//

	class HttpServerSettings;
	class HttpServer;

	typedef bool (*init_plugin_function) (const aconnect::str2str_map& params, int pluginIndex, 
		HttpServer* server);
	typedef void (*destroy_plugin_function) ();

	typedef std::map <string, struct DirectorySettings> directories_map;
	typedef std::vector<std::pair<bool, string> > default_documents_vector;
	typedef std::map<ModuleCallbackType, std::list< std::pair <void*, int> > > callback_map;
	typedef std::map<int, callback_map > directories_callback_map;
	
	typedef std::list <PluginRegistrationInfo> directory_plugins_list; 
	
	// key - registered plugin name, value - plugin registartion info
	typedef std::map <string, struct PluginInfo> global_plugins_map;

	typedef std::vector<std::pair<boost::regex, string> > mappings_vector;

	//==================================================================//

	namespace settings_defaults
	{
		const bool EnableKeepAlive		= true;	
		const bool EnableGzipDeflate	= true;	
		const int KeepAliveTimeout		= 5;	// sec
		const int ServerSocketTimeout	= 900;	// sec
		const int CommandSocketTimeout	= 30;	// sec
		const size_t ResponseBufferSize	= 2 * 1024 * 1024;	// bytes
		const size_t MaxChunkSize				= 65535;	// bytes
		const size_t MaxRequestSize				= 2097152;	// bytes (2 Mb)

		const int UploadCreationTriesCount	= 10;

		string_constant ServerVersion = "ahttpserver/0.30";
		string_constant DirectoryConfigFile = "directory.config";
		string_constant MessagesConfigFile = "messages.config";

		// Default drectory browsing HTML templates
		/*
			<!-- Record attributes: 
				{name} - name of item, 
				{size} - size of item in kb (min. length of field - 16 symbols, hardcoded),
				{url} - url to open item
				{time} - last modify dat/time of item,
				{page-url} - url to current page
				{parent-url} - url to parent directory
				{files-count} - files count in current directory
				{directories-count} - sub-directories count in current directory
				{errors-count} - reading errors count 
				{tab} - will be replaced with "\t"
				{eol} - will be replaced with "\r\n"
		-->
		*/
		string_constant HeaderTemplate = "<html>\
			<head>\
				<title>{page-url}</title>\
				<style>BODY, PRE { font: 10pt \"Lucida Console\", \"Courier New\", monospace; }</style>\
			</head>\
			<body>\
<pre>\
<b>Directory: <i>{page-url}</i></b>\r\n\r\n";

		string_constant ParentDirectoryTemplate = "<a href=\"{parent-url}\">[parent directory]</a>\r\n\r\n";
		
		string_constant DirectoryTemplate = "{time}\t\tdirectory\t\t<a href=\"{url}\">{name}</a>\r\n";
		
		string_constant VirtualDirectoryTemplate = "{time}\t\t  virtual\t\t<a href=\"{url}\">{name}</a>\r\n";
        
		string_constant FileTemplate = "{time}\t{size}\t\t<a href=\"{url}\">{name}</a>\r\n";
        
		string_constant FooterTemplate = "\r\n\
Files: {files-count}\r\n\
Directories: {directories-count}\r\n\
Reading errors: {errors-count}\r\n\
</pre></body></html>";
	}

	//==================================================================//
	
	struct DirectorySettings
	{
		DirectorySettings ();
		
		int number;
		string name;
		string parentName;
		string relativePath;	// virtual path from parent
		string virtualPath;		// full virtual path
		string realPath;		// real physical path
		Tristate::TristateEnum browsingEnabled;
		Tristate::TristateEnum showHiddenFiles;

		bool isVirtualDirectory;
		string charset;

		default_documents_vector defaultDocuments; // bool - add/remove (false/true)
		
		directory_plugins_list handlers;
		directory_plugins_list modules;

		mappings_vector	mappings;
		
		string headerTemplate,
			directoryTemplate,
			parentDirectoryTemplate,
			virtualDirectoryTemplate,
			fileTemplate,
			footerTemplate;

		size_t maxRequestSize;
		Tristate::TristateEnum enableParentPathAccess; 

		HttpServerSettings* parentServerSettings;
		aconnect::str2str_map	customParams;

		inline bool isParentPathAccessEnabled() const { return enableParentPathAccess == Tristate::True; };
		void processTemplates();
	};

	//==================================================================//

	class HttpServerSettings
	{
	public:
		
		HttpServerSettings();
		~HttpServerSettings();

		//==================================================================//
		//	Simple properties												//
		//==================================================================//
		inline aconnect::port_type port() const					{ assert (_port != -1);			return _port;	}
		inline void setPort(aconnect::port_type port)			{ _port = port;									}
		
		inline const aconnect::port_type commandPort() const	{	return _commandPort;						}
		inline void setCommandPort(aconnect::port_type port)	{	_commandPort = port;						}
		
		inline const string& root() const						{			return _rootDirName;				}
		inline void setRootDirectoryName(string root)			{			_rootDirName.swap(root);			}
		
		inline aconnect::Logger* logger()						{		return _logger;							}
		inline void setLogger (aconnect::Logger* logger)		{		_logger = logger;						}

		inline const string serverVersion() const				{		return _serverVersion;					}
		inline void setServerVersion (string version)			{		_serverVersion = version;				}

		inline const directories_map& Directories() const		{		return _directories;					}
				
		inline const DirectorySettings& getRootDirSettings() const	{		
			directories_map::const_iterator rootRecord = _directories.find ("/");
 			if (rootRecord == _directories.end()) 
				throw std::runtime_error ("Root web directory (\"/\") is not registered");
			
			return rootRecord->second;			
		}

		const DirectorySettings* getDirSettingsByName(string_constref dirName) const;
		const DirectorySettings* getDirSettings(int number) const;

		const directories_callback_map& modulesCallbacks () const	{		return _modulesCallbacks;		}

		static void addModuleCallbacksToMap (void** moduleCallbacks, int pluginIndex, callback_map& callbackMap );
		
		string getMimeType (string_constref ext) const;
		void loadMimeType (string_constref ext, string_constptr typeDesc);

		void setupDefaultMimeTypes ();



		string_constref getMessage (string_constref key) const throw (std::range_error);
		void loadMessage (string_constref key, string_constptr value);

		void initPlugins (PluginType pluginType, HttpServer* server);
		void destroyPlugins (PluginType pluginType, bool unload = true);

		
		//==================================================================//
		//	Helpers															//
		//==================================================================//
		static std::set<string> parseExtensions(string_constref ext);
		void updateAppLocationInPath (aconnect::string &pathStr) const;
		
	public:
		//==================================================================//
		//	Public fields													//
		//==================================================================//
		// Not used now, reserved to implement slave 
		// processes for directories (like App. Pools in IIS)
		int InstanceId; 

		aconnect::ServerSettings ConnectionSettings;

		bool	IsKeepAliveEnabled;
		int		KeepAliveTimeout;

		bool	IsGzipDeflateEnabled;
		
		int		CommandSocketTimeout;

		string	AppLocation;
		string	GlobalUploadsDirectory;
		int		UploadCreationTriesCount;

		// HTTP settings
		size_t ResponseBufferSize;
		size_t MaxChunkSize;

		global_plugins_map RegisteredHandlers;
		global_plugins_map RegisteredModules;



	public:
		//==================================================================//
		//	External info loaders											//
		//==================================================================//
		void resetDirectoriesInfo () {
			_directories.clear();
		}

		
		void loadRootDirectory (std::vector <DirectorySettings>& directoriesList, 
			boost::function1<void, DirectorySettings* > locaInfoLoadCallback) throw (settings_load_error);

		void setupRootDirectory (DirectorySettings &directoryInfo,
			boost::function1<void, DirectorySettings* > locaInfoLoadCallback) throw (settings_load_error);

		void loadPluginProperties (string_constref pluginName, PluginInfo& info, PluginType pluginType) 
			throw (settings_load_error);
		
		void fillModulesCallbackInfo ();

		void copyHandlersRegistration (const DirectorySettings& parent, DirectorySettings& child);

		void copyModulesRegistration (const DirectorySettings& parent, DirectorySettings& child);

		//==================================================================//
		//	Environment preparation											//
		//==================================================================//
		
		void applyLocale (string_constref localeName);

		void setupGlobalUploadsDir ();
		
	protected:
		
		void loadGlobalModules (DirectorySettings& root);

		void fillDirectoriesMap (std::vector <ahttp::DirectorySettings>& directoriesList, 
			std::vector <ahttp::DirectorySettings>::iterator parent,
			boost::function1<void, DirectorySettings* > locaInfoLoadCallback);

		void initMessages ();

	protected:
		
		aconnect::port_type _port;
		aconnect::port_type _commandPort;
		string _rootDirName;

		directories_map _directories;
		aconnect::str2str_map _mimeTypes;
		aconnect::str2str_map _messages;

		aconnect::Logger*	_logger;
		string	_serverVersion;

		// key - directory::number, value - list of callbacks
		directories_callback_map _modulesCallbacks;
	};
}
#endif // AHTTP_SERVER_SETTINGS_H
